package com.jacky.javafx.controller;

import com.jacky.javafx.graph.Animation;
import com.jacky.javafx.graph.DrawShapes;
import com.jacky.javafx.graph.Graph;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.util.Pair;

import javax.xml.stream.events.StartDocument;
import java.util.ArrayList;
import java.util.Set;

public class GraphController {

    private final Graph graph = new Graph();

    @FXML
    private AnchorPane graphArea;
    @FXML
    private AnchorPane gridMatrixArea;

    @FXML
    private TextField sourceField;
    @FXML
    private TextField destinationField;
    @FXML
    private TextField weightField;

    @FXML
    private Slider speedSlider;
    @FXML
    private Label algorithmStatus;

    private int count = 0;
    private Circle initialSelectedCircle = null;

    public void animateBFS() {
        if (sourceField.getText().isEmpty() || destinationField.getText().isEmpty()) {
            Alert alert = new Alert(Alert.AlertType.ERROR);
            String content = "Please fill all requested fields. i.e: source, destination";
            alert.setContentText(content);
            alert.showAndWait();
            return;
        }

        int source;
        int destination;
        try {
            source = Integer.parseInt(sourceField.getText());
            destination = Integer.parseInt(destinationField.getText());
            if (source < 0 || source >= count || destination < 0 || destination >= count) {
                throw new Exception();
            }
        } catch (Exception e) {
            Alert alert = new Alert(Alert.AlertType.ERROR);
            String content = "Please enter valid input in Source and Destination Field";
            alert.setContentText(content);
            alert.showAndWait();
            return;
        }

        System.out.println("BFS:" + source + " -> " + destination);
        Pair<ArrayList<ArrayList<Integer>>, ArrayList<Integer>> result = graph.bfs(source, destination);
        ArrayList<ArrayList<Integer>> layers = result.getKey();
        ArrayList<Integer> reversePath = result.getValue();

        Animation.animateBFS(source, destination, layers, reversePath, graphArea);

        if (!reversePath.isEmpty()) {
            algorithmStatus.setText("Status: BFS Completed");
        } else {
            algorithmStatus.setText("Status: Source and destination are not connected!");
        }
    }

    public void animateDFS() {
        if (sourceField.getText().isEmpty() || destinationField.getText().isEmpty()) {
            Alert alert = new Alert(Alert.AlertType.ERROR);
            String content = "Please fill all requested fields. i.e: source, destination";
            alert.setContentText(content);
            alert.showAndWait();
            return;
        }

        int source;
        int destination;
        try {
            source = Integer.parseInt(sourceField.getText());
            destination = Integer.parseInt(destinationField.getText());
            if (source < 0 || source >= count || destination < 0 || destination >= count) {
                throw new Exception();
            }
        } catch (Exception e) {
            Alert alert = new Alert(Alert.AlertType.ERROR);
            String content = "Please enter valid input in Source and Destination Field";
            alert.setContentText(content);
            alert.showAndWait();
            return;
        }

        System.out.println("DFS:" + source + " -> " + destination);
        Pair<ArrayList<Integer>, Boolean> result = graph.dfs(source, destination);
        ArrayList<Integer> layers = result.getKey();
        Boolean isDestinationFound = result.getValue();

        Animation.animateDFS(source, destination, layers, graphArea);

        if (isDestinationFound) {
            algorithmStatus.setText("Status: DFS Completed");
        } else {
            algorithmStatus.setText("Status: No Connection between Source and Destination");
        }
    }

    public void animateDijkstra() {
        if (sourceField.getText().isEmpty() || destinationField.getText().isEmpty()) {
            Alert alert = new Alert(Alert.AlertType.ERROR);
            String content = "Please fill all requested fields. i.e: source, destination";
            alert.setContentText(content);
            alert.showAndWait();
            return;
        }

        int source;
        int destination;
        try {
            source = Integer.parseInt(sourceField.getText());
            destination = Integer.parseInt(destinationField.getText());
            if (source < 0 || source >= count || destination < 0 || destination >= count) {
                throw new Exception();
            }
        } catch (Exception e) {
            Alert alert = new Alert(Alert.AlertType.ERROR);
            String content = "Please enter valid input in Source and Destination Field";
            alert.setContentText(content);
            alert.showAndWait();
            return;
        }

        System.out.println("DFS:" + source + " -> " + destination);
        Pair<ArrayList<Integer>, ArrayList<Integer>> dijkstra = graph.dijkstra(source, destination);
        ArrayList<Integer> layers = dijkstra.getKey();
        ArrayList<Integer> reversePath = dijkstra.getValue();

        Animation.animateDijkstra(source, destination, layers, reversePath, graphArea);

        if (!reversePath.isEmpty()) {
            algorithmStatus.setText("Status: Dijkstra Completed");
        } else {
            algorithmStatus.setText("Status: Source and destination are not connected!");
        }
    }

    public void addCircle(MouseEvent e) {
        System.out.println("add circle:" + e.getTarget());
        if (e.getTarget() != graphArea) {
            return;
        }

        System.out.println("Creating a Circle");
        DrawShapes.drawNode(e.getX(), e.getY(), count, Color.LIGHTSKYBLUE, graphArea);
        Circle circle = (Circle) graphArea.lookup("#circle_" + count);
        circle.setOnMouseClicked(evt -> createEdge((Circle) evt.getTarget()));

        count++;
        graph.insertVertx();
        graph.display();

        DrawShapes.drawMatrix(graph.getMatrix(), graph.getVerticesCount(), gridMatrixArea);
    }

    private void createEdge(Circle circle) {
        if (initialSelectedCircle == null) {
            System.out.println("First Circle Selected");
            initialSelectedCircle = circle;
            circle.setFill(Color.CRIMSON);
        } else {
            if (initialSelectedCircle == circle) {
                return;
            }
            System.out.println("Second Circle Selected and Drawing the edge");
            if (weightField.getText().isEmpty()) {
                Alert alert = new Alert(Alert.AlertType.ERROR);
                String content = "Please fill the weight field!";
                alert.setContentText(content);
                alert.showAndWait();
                return;
            }
            int edgeWeight;
            try {
                edgeWeight = Integer.parseInt(weightField.getText());
                if (edgeWeight <= 0) {
                    throw new Exception();
                }
            } catch (Exception e) {
                Alert alert = new Alert(Alert.AlertType.ERROR);
                String content = "Edge weight cannot be negative";
                alert.setContentText(content);
                alert.showAndWait();
                return;
            }

            DrawShapes.drawEdge(initialSelectedCircle, circle, edgeWeight, graphArea);

            int u = Integer.parseInt(initialSelectedCircle.getId().split("_")[1]);
            int v = Integer.parseInt(circle.getId().split("_")[1]);
            graph.insertEdge(u, v, edgeWeight);
            graph.display();

            initialSelectedCircle.setFill(Color.LIGHTSKYBLUE);
            initialSelectedCircle = null;

            DrawShapes.drawMatrix(graph.getMatrix(), graph.getVerticesCount(), gridMatrixArea);
        }
    }

    public void showComponents() {
        ArrayList<ArrayList<Integer>> components = graph.getComponents();
        Animation.animateComponents(components, graphArea);
    }

    public void showSpanningTree() {
        ArrayList<Pair<Integer, Integer>> components = graph.getSpanningTree();
        if (!components.isEmpty()) {
            algorithmStatus.setText("Found the Minimum Spanning Tree using Kruskal");
            Animation.animateSpanningTree(components, graphArea);
        } else {
            algorithmStatus.setText("Cannot find Minimum Spanning Tree");
        }
    }

    public void showBridges() {
        ArrayList<Pair<Integer, Integer>> bridges = graph.getBridges();
        if (!bridges.isEmpty()) {
            algorithmStatus.setText("Calculated All the Bridges in the Graph");
            Animation.animateBridges(bridges, graphArea);
        } else {
            algorithmStatus.setText("No Bridges Found in the graph");
        }
    }

    public void showArticulationPoints() {
        Set<Integer> articulationPoints = graph.getArticulationPoints();
        if (!articulationPoints.isEmpty()) {
            algorithmStatus.setText("Calculated All Articulation Points in the Graph");
            Animation.animateArticulationPoints(articulationPoints, graphArea);
        } else {
            algorithmStatus.setText("No Articulation Point Found in the graph");
        }
    }

    public void resetGraph() {
        count = 0;
        graph.reset();

        Label headerText = (Label) graphArea.lookup("#headerText");
        graphArea.getChildren().clear();
        graphArea.getChildren().add(headerText);

        DrawShapes.drawMatrix(graph.getMatrix(), graph.getVerticesCount(), gridMatrixArea);
        initialSelectedCircle = null;
        algorithmStatus.setText("");
    }

    public void clearGraph() {
        initialSelectedCircle = null;
        for (Node node : graphArea.getChildren()) {
            if (node instanceof Line line) {
                line.setStroke(Color.BLACK);
                line.setStrokeWidth(3);
                line.setViewOrder(100);
            } else if (node instanceof Circle circle) {
                circle.setStrokeWidth(0);
                circle.setFill(Color.LIGHTSKYBLUE);
            }
        }

        algorithmStatus.setText("");
    }

    public void setAnimationSpeed() {
        Animation.setSliderSpeed((int) speedSlider.getValue());
    }
}
